<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 3.9.0">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"way1989.github.io","root":"/","scheme":"Mist","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":true},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="Android深入浅出 Retrofit">
<meta name="keywords" content="博客,Retrofit">
<meta property="og:type" content="article">
<meta property="og:title" content="Android深入浅出 Retrofit">
<meta property="og:url" content="https://way1989.github.io/2016/09/29/blogs/Retrofit/index.html">
<meta property="og:site_name" content="Broncho">
<meta property="og:description" content="Android深入浅出 Retrofit">
<meta property="og:locale" content="zh-CN">
<meta property="og:image" content="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/1.jpg">
<meta property="og:image" content="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/2.jpg">
<meta property="og:image" content="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/3.jpg">
<meta property="og:image" content="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/4.jpg">
<meta property="og:image" content="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/5.jpg">
<meta property="og:updated_time" content="2020-08-15T07:52:32.560Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Android深入浅出 Retrofit">
<meta name="twitter:description" content="Android深入浅出 Retrofit">
<meta name="twitter:image" content="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/1.jpg">

<link rel="canonical" href="https://way1989.github.io/2016/09/29/blogs/Retrofit/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>Android深入浅出 Retrofit | Broncho</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">Broncho</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>
  <div class="reading-progress-bar"></div>

  <a href="https://github.com/way1989" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://way1989.github.io/2016/09/29/blogs/Retrofit/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.jpg">
      <meta itemprop="name" content="Broncho">
      <meta itemprop="description" content="爱生活...爱Android...">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Broncho">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          Android深入浅出 Retrofit
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2016-09-29 23:33:48" itemprop="dateCreated datePublished" datetime="2016-09-29T23:33:48+08:00">2016-09-29</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2020-08-15 15:52:32" itemprop="dateModified" datetime="2020-08-15T15:52:32+08:00">2020-08-15</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/博客/" itemprop="url" rel="index"><span itemprop="name">博客</span></a>
                </span>
                  ，
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/博客/Retrofit/" itemprop="url" rel="index"><span itemprop="name">Retrofit</span></a>
                </span>
            </span>

          
            <div class="post-description">Android深入浅出 Retrofit</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <h3 id="本文转自微信-Bugly"><a href="#本文转自微信-Bugly" class="headerlink" title="本文转自微信: Bugly"></a>本文转自微信: <a href="http://mp.weixin.qq.com/s/5EmNqqX7rt1KT62KXNCq3A" target="_blank" rel="noopener">Bugly</a></h3><p><img src="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/1.jpg"></p>
<p>Android 开发中，从原生的 HttpUrlConnection 到经典的 Apache 的 HttpClient，再到对前面这些网络基础框架的封装，比如 Volley、Async Http Client，Http 相关开源框架的选择还是很多的，其中由著名的 Square 公司开源的 Retrofit 更是以其简易的接口配置、强大的扩展支持、优雅的代码结构受到大家的追捧。也正是由于 Square 家的框架一如既往的简洁优雅，所以我一直在想，Square 公司是不是只招处女座的程序员？</p>
<h2 id="初识-Retrofit"><a href="#初识-Retrofit" class="headerlink" title="初识 Retrofit"></a>初识 Retrofit</h2><p>单从 Retrofit 这个单词，你似乎看不出它究竟是干嘛的，当然，我也看不出来 :)逃。。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Retrofitting refers to the addition of new technology or features to older systems.</span><br><span class="line">—From Wikipedia</span><br></pre></td></tr></table></figure></p>
<p>于是我们就明白了，冠以 Retrofit 这个名字的这个家伙，应该是某某某的 『Plus』 版本了。</p>
<h3 id="Retrofit-概览"><a href="#Retrofit-概览" class="headerlink" title="Retrofit 概览"></a>Retrofit 概览</h3><p>Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。注意这里并没有说它是网络请求框架，主要原因在于网络请求的工作并不是 Retrofit 来完成的。Retrofit 2.0 开始内置 OkHttp，前者专注于接口的封装，后者专注于网络请求的高效，二者分工协作，宛如古人的『你耕地来我织布』，小日子别提多幸福了。<br><img src="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/2.jpg"><br>我们的应用程序通过 Retrofit 请求网络，实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息，之后由 OkHttp 完成后续的请求操作，在服务端返回数据之后，OkHttp 将原始的结果交给 Retrofit，后者根据用户的需求对结果进行解析的过程。<br>讲到这里，你就会发现所谓 Retrofit，其实就是 Retrofitting OkHttp 了。</p>
<h3 id="Hello-Retrofit"><a href="#Hello-Retrofit" class="headerlink" title="Hello Retrofit"></a>Hello Retrofit</h3><p>多说无益，不要来段代码陶醉一下。使用 Retrofit 非常简单，首先你需要在你的 build.gradle 中添加依赖：<br><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">compile <span class="string">'com.squareup.retrofit2:retrofit:2.0.2'</span></span><br></pre></td></tr></table></figure></p>
<p>你一定是想要访问 GitHub 的 api 对吧，那么我们就定义一个接口：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">GitHubService</span> </span>&#123;</span><br><span class="line">  <span class="meta">@GET</span>(<span class="string">"users/&#123;user&#125;/repos"</span>)</span><br><span class="line">  Call&lt;List&lt;Repo&gt;&gt; listRepos(<span class="meta">@Path</span>(<span class="string">"user"</span>) String user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>接口当中的 listRepos 方法，就是我们想要访问的 api 了：<br><code>https://api.github.com/users/{user}/repos</code><br>其中，在发起请求时， {user} 会被替换为方法的第一个参数 user。<br>好，现在接口有了，我们要构造 Retrofit 了：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Retrofit retrofit = <span class="keyword">new</span> Retrofit.Builder()</span><br><span class="line">    .baseUrl(<span class="string">"https://api.github.com/"</span>)</span><br><span class="line">    .build();</span><br><span class="line"> </span><br><span class="line">GitHubService service = retrofit.create(GitHubService.class);</span><br></pre></td></tr></table></figure></p>
<p>这里的 service 就好比我们的快递哥，还是往返的那种哈~<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Call&lt;List&lt;Repo&gt;&gt; repos = service.listRepos(<span class="string">"octocat"</span>);</span><br></pre></td></tr></table></figure></p>
<p>发请求的代码就像前面这一句，返回的 repos 其实并不是真正的数据结果，它更像一条指令，你可以在合适的时机去执行它：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 同步调用</span></span><br><span class="line">List&lt;Repo&gt; data = repos.execute(); </span><br><span class="line"> </span><br><span class="line"><span class="comment">// 异步调用</span></span><br><span class="line">repos.enqueue(<span class="keyword">new</span> Callback&lt;List&lt;Repo&gt;&gt;() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onResponse</span><span class="params">(Call&lt;List&lt;Repo&gt;&gt; call, Response&lt;List&lt;Repo&gt;&gt; response)</span> </span>&#123;</span><br><span class="line">                List&lt;Repo&gt; data = response.body();</span><br><span class="line">            &#125;</span><br><span class="line"> </span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onFailure</span><span class="params">(Call&lt;List&lt;Repo&gt;&gt; call, Throwable t)</span> </span>&#123;</span><br><span class="line">                t.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br></pre></td></tr></table></figure></p>
<p>啥感觉？有没有突然觉得请求接口就好像访问自家的方法一样简单？呐，前面我们看到的，就是 Retrofit 官方的 demo 了。你以为这就够了？噗~怎么可能。。</p>
<h3 id="Url-配置"><a href="#Url-配置" class="headerlink" title="Url 配置"></a>Url 配置</h3><p>Retrofit 支持的协议包括 GET/POST/PUT/DELETE/HEAD/PATCH，当然你也可以直接用 HTTP 来自定义请求。这些协议均以注解的形式进行配置，比如我们已经见过 GET 的用法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GET</span>(<span class="string">"users/&#123;user&#125;/repos"</span>)</span><br><span class="line">Call&lt;List&lt;Repo&gt;&gt; listRepos(<span class="meta">@Path</span>(<span class="string">"user"</span>) String user);</span><br></pre></td></tr></table></figure></p>
<p>这些注解都有一个参数 value，用来配置其路径，比如示例中的 users/{user}/repos，我们还注意到在构造 Retrofit 之时我们还传入了一个 baseUrl(“<a href="https://api.github.com/&quot;)，请求的完整" target="_blank" rel="noopener">https://api.github.com/&quot;)，请求的完整</a> Url 就是通过 baseUrl 与注解的 value（下面称 “path“ ） 整合起来的，具体整合的规则如下：</p>
<ul>
<li>path 是绝对路径的形式：<br>   path = “/apath”，baseUrl = “<a href="http://host:port/a/b&quot;" target="_blank" rel="noopener">http://host:port/a/b&quot;</a><br>  Url = “<a href="http://host:port/apath&quot;" target="_blank" rel="noopener">http://host:port/apath&quot;</a></li>
<li>path 是相对路径，baseUrl 是目录形式：<br>  path = “apath”，baseUrl = “<a href="http://host:port/a/b/&quot;" target="_blank" rel="noopener">http://host:port/a/b/&quot;</a><br>   Url = “<a href="http://host:port/a/b/apath&quot;" target="_blank" rel="noopener">http://host:port/a/b/apath&quot;</a></li>
<li>path 是相对路径，baseUrl 是文件形式：<br>   path = “apath”，baseUrl = “<a href="http://host:port/a/b&quot;" target="_blank" rel="noopener">http://host:port/a/b&quot;</a><br>   Url = “<a href="http://host:port/a/apath&quot;" target="_blank" rel="noopener">http://host:port/a/apath&quot;</a></li>
<li>path 是完整的 Url：<br>   path = “<a href="http://host:port/aa/apath&quot;，baseUrl" target="_blank" rel="noopener">http://host:port/aa/apath&quot;，baseUrl</a> = “<a href="http://host:port/a/b&quot;" target="_blank" rel="noopener">http://host:port/a/b&quot;</a><br>   Url = “<a href="http://host:port/aa/apath&quot;" target="_blank" rel="noopener">http://host:port/aa/apath&quot;</a></li>
</ul>
<p>建议采用第二种方式来配置，并尽量使用同一种路径形式。如果你在代码里面混合采用了多种配置形式，恰好赶上你哪天头晕眼花，信不信分分钟写一堆 bug 啊哈哈。</p>
<h3 id="参数类型"><a href="#参数类型" class="headerlink" title="参数类型"></a>参数类型</h3><p>发请求时，需要传入参数，Retrofit 通过注解的形式令 Http 请求的参数变得更加直接，而且类型安全。</p>
<h4 id="Query-amp-QueryMap"><a href="#Query-amp-QueryMap" class="headerlink" title="Query &amp; QueryMap"></a>Query &amp; QueryMap</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GET</span>(<span class="string">"/list"</span>)</span><br><span class="line"><span class="function">Call&lt;ResponseBody&gt; <span class="title">list</span><span class="params">(@Query(<span class="string">"page"</span>)</span> <span class="keyword">int</span> page)</span>;</span><br></pre></td></tr></table></figure>
<p>Query 其实就是 Url 中 ‘?’ 后面的 key-value，比如：<br><code>http://www.println.net/?cate=android</code><br>这里的 cate=android 就是一个 Query，而我们在配置它的时候只需要在接口方法中增加一个参数，即可：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">PrintlnServer</span></span>&#123;</span><br><span class="line">    <span class="meta">@GET</span>(<span class="string">"/"</span>)</span><br><span class="line">    <span class="function">Call&lt;String&gt; <span class="title">cate</span><span class="params">(@Query(<span class="string">"cate"</span>)</span> String cate)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这时候你肯定想，如果我有很多个 Query，这么一个个写岂不是很累？而且根据不同的情况，有些字段可能不传，这与方法的参数要求显然也不相符。于是，打群架版本的 QueryMap 横空出世了，使用方法很简单，我就不多说了。</p>
<h4 id="Field-amp-FieldMap"><a href="#Field-amp-FieldMap" class="headerlink" title="Field &amp; FieldMap"></a>Field &amp; FieldMap</h4><p>其实我们用 POST 的场景相对较多，绝大多数的服务端接口都需要做加密、鉴权和校验，GET 显然不能很好的满足这个需求。使用 POST 提交表单的场景就更是刚需了，怎么提呢？<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@FormUrlEncoded</span></span><br><span class="line"><span class="meta">@POST</span>(<span class="string">"/"</span>)</span><br><span class="line"><span class="function">Call&lt;ResponseBody&gt; <span class="title">example</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    @Field(<span class="string">"name"</span>)</span> String name,</span></span><br><span class="line"><span class="function">    @<span class="title">Field</span><span class="params">(<span class="string">"occupation"</span>)</span> String occupation)</span>;</span><br></pre></td></tr></table></figure></p>
<p>其实也很简单，我们只需要定义上面的接口就可以了，我们用 Field 声明了表单的项，这样提交表单就跟普通的函数调用一样简单直接了。<br>等等，你说你的表单项不确定个数？还是说有很多项你懒得写？Field 同样有个打群架的版本——FieldMap，赶紧试试吧~~</p>
<h4 id="Part-amp-PartMap"><a href="#Part-amp-PartMap" class="headerlink" title="Part &amp; PartMap"></a>Part &amp; PartMap</h4><p>这个是用来上传文件的。话说当年用 HttpClient 上传个文件老费劲了，一会儿编码不对，一会儿参数错误（也怪那时段位太低吧TT）。。。可是现在不同了，自从有了 Retrofit，妈妈再也不用担心文件上传费劲了~~~<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">FileUploadService</span> </span>&#123;  </span><br><span class="line">    <span class="meta">@Multipart</span></span><br><span class="line">    <span class="meta">@POST</span>(<span class="string">"upload"</span>)</span><br><span class="line">    <span class="function">Call&lt;ResponseBody&gt; <span class="title">upload</span><span class="params">(@Part(<span class="string">"description"</span>)</span> RequestBody description,</span></span><br><span class="line"><span class="function">                              @Part MultipartBody.Part file)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>如果你需要上传文件，和我们前面的做法类似，定义一个接口方法，需要注意的是，这个方法不再有 @FormUrlEncoded 这个注解，而换成了 @Multipart，后面只需要在参数中增加 Part 就可以了。也许你会问，这里的 Part 和 Field 究竟有什么区别，其实从功能上讲，无非就是客户端向服务端发起请求携带参数的方式不同，并且前者可以携带的参数类型更加丰富，包括数据流。也正是因为这一点，我们可以通过这种方式来上传文件，下面我们就给出这个接口的使用方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//先创建 service</span></span><br><span class="line">FileUploadService service = retrofit.create(FileUploadService.class);</span><br><span class="line"> </span><br><span class="line"><span class="comment">//构建要上传的文件</span></span><br><span class="line">File file = <span class="keyword">new</span> File(filename);</span><br><span class="line">RequestBody requestFile =</span><br><span class="line">        RequestBody.create(MediaType.parse(<span class="string">"application/otcet-stream"</span>), file);</span><br><span class="line"> </span><br><span class="line">MultipartBody.Part body =</span><br><span class="line">        MultipartBody.Part.createFormData(<span class="string">"aFile"</span>, file.getName(), requestFile);</span><br><span class="line"> </span><br><span class="line">String descriptionString = <span class="string">"This is a description"</span>;</span><br><span class="line">RequestBody description =</span><br><span class="line">        RequestBody.create(</span><br><span class="line">                MediaType.parse(<span class="string">"multipart/form-data"</span>), descriptionString);</span><br><span class="line"> </span><br><span class="line">Call&lt;ResponseBody&gt; call = service.upload(description, body);</span><br><span class="line">call.enqueue(<span class="keyword">new</span> Callback&lt;ResponseBody&gt;() &#123;</span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onResponse</span><span class="params">(Call&lt;ResponseBody&gt; call,</span></span></span><br><span class="line"><span class="function"><span class="params">                         Response&lt;ResponseBody&gt; response)</span> </span>&#123;</span><br><span class="line">    System.out.println(<span class="string">"success"</span>);</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onFailure</span><span class="params">(Call&lt;ResponseBody&gt; call, Throwable t)</span> </span>&#123;</span><br><span class="line">    t.printStackTrace();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></p>
<p>在实验时，我上传了一个只包含一行文字的文件：<br>Visit me: <a href="http://www.println.net" target="_blank" rel="noopener">http://www.println.net</a><br>那么我们去服务端看下我们的请求是什么样的：<br>HEADERS<br><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Accept-Encoding: gzip</span><br><span class="line">Content-Length: 470</span><br><span class="line">Content-Type: multipart/form-data; boundary=9b670d44-63dc-4a8a-833d-66e45e0156ca</span><br><span class="line">User-Agent: okhttp/3.2.0</span><br><span class="line">X-Request-Id: 9d70e8cc-958b-4f42-b979-4c1fcd474352</span><br><span class="line">Via: 1.1 vegur</span><br><span class="line">Host: requestb.in</span><br><span class="line">Total-Route-Time: 0</span><br><span class="line">Connection: close</span><br><span class="line">Connect-Time: 0</span><br></pre></td></tr></table></figure></p>
<p>FORM/POST PARAMETERS<br><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">description: This is a description</span><br></pre></td></tr></table></figure></p>
<p>RAW BODY<br><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">--9b670d44-63dc-4a8a-833d-66e45e0156ca</span><br><span class="line">Content-Disposition: form-data; name="description"</span><br><span class="line">Content-Transfer-Encoding: binary</span><br><span class="line">Content-Type: multipart/form-data; charset=utf-8</span><br><span class="line">Content-Length: 21</span><br><span class="line"></span><br><span class="line">This is a description</span><br><span class="line">--9b670d44-63dc-4a8a-833d-66e45e0156ca</span><br><span class="line">Content-Disposition: form-data; name="aFile"; filename="uploadedfile.txt"</span><br><span class="line">Content-Type: application/otcet-stream</span><br><span class="line">Content-Length: 32</span><br><span class="line"></span><br><span class="line">Visit me: http://www.println.net</span><br><span class="line">--9b670d44-63dc-4a8a-833d-66e45e0156ca--</span><br></pre></td></tr></table></figure></p>
<p>我们看到，我们上传的文件的内容出现在请求当中了。如果你需要上传多个文件，就声明多个 Part 参数，或者试试 PartMap。</p>
<h3 id="Converter，让你的入参和返回类型丰富起来"><a href="#Converter，让你的入参和返回类型丰富起来" class="headerlink" title="Converter，让你的入参和返回类型丰富起来"></a>Converter，让你的入参和返回类型丰富起来</h3><h4 id="RequestBodyConverter"><a href="#RequestBodyConverter" class="headerlink" title="RequestBodyConverter"></a>RequestBodyConverter</h4><p>前面我为大家展示了如何用 Retrofit 上传文件，这个上传的过程其实。。还是有那么点儿不够简练，我们只是要提供一个文件用于上传，可我们前后构造了三个对象：<br><img src="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/3.jpg"><br>天哪，肯定是哪里出了问题。实际上，Retrofit 允许我们自己定义入参和返回的类型，不过，如果这些类型比较特别，我们还需要准备相应的 Converter，也正是因为 Converter 的存在， Retrofit 在入参和返回类型上表现得非常灵活。<br>下面我们把刚才的 Service 代码稍作修改：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">FileUploadService</span> </span>&#123;  </span><br><span class="line">    <span class="meta">@Multipart</span></span><br><span class="line">    <span class="meta">@POST</span>(<span class="string">"upload"</span>)</span><br><span class="line">    <span class="function">Call&lt;ResponseBody&gt; <span class="title">upload</span><span class="params">(@Part(<span class="string">"description"</span>)</span> RequestBody description,</span></span><br><span class="line"><span class="function">        <span class="comment">//注意这里的参数 "aFile" 之前是在创建 MultipartBody.Part 的时候传入的</span></span></span><br><span class="line"><span class="function">        @<span class="title">Part</span><span class="params">(<span class="string">"aFile"</span>)</span> File file)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>现在我们把入参类型改成了我们熟悉的 File，如果你就这么拿去发请求，服务端收到的结果会让你哭了的。。。</p>
<p>RAW BODY<br><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">--7d24e78e-4354-4ed4-9db4-57d799b6efb7</span><br><span class="line">Content-Disposition: form-data; name="description"</span><br><span class="line">Content-Transfer-Encoding: binary</span><br><span class="line">Content-Type: multipart/form-data; charset=utf-8</span><br><span class="line">Content-Length: 21</span><br><span class="line"></span><br><span class="line">This is a description</span><br><span class="line">--7d24e78e-4354-4ed4-9db4-57d799b6efb7</span><br><span class="line">Content-Disposition: form-data; name="aFile"</span><br><span class="line">Content-Transfer-Encoding: binary</span><br><span class="line">Content-Type: application/json; charset=UTF-8</span><br><span class="line">Content-Length: 35</span><br><span class="line"></span><br><span class="line">// 注意这里！！之前是文件的内容，现在变成了文件的路径</span><br><span class="line">&#123;"path":"samples/uploadedfile.txt"&#125; </span><br><span class="line">--7d24e78e-4354-4ed4-9db4-57d799b6efb7--</span><br></pre></td></tr></table></figure></p>
<p>服务端收到了一个文件的路径，它肯定会觉得<br><img src="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/4.jpg"><br>好了，不闹了，这明显是 Retrofit 在发现自己收到的实际入参是个 File 时，不知道该怎么办，情急之下给 toString了，而且还是个 JsonString（后来查证原来是使用了 GsonRequestBodyConverter。。）。</p>
<p>接下来我们就自己实现一个 FileRequestBodyConverter，<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">FileRequestBodyConverterFactory</span> <span class="keyword">extends</span> <span class="title">Converter</span>.<span class="title">Factory</span> </span>&#123;</span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> Converter&lt;File, RequestBody&gt; <span class="title">requestBodyConverter</span><span class="params">(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> FileRequestBodyConverter();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">FileRequestBodyConverter</span> <span class="keyword">implements</span> <span class="title">Converter</span>&lt;<span class="title">File</span>, <span class="title">RequestBody</span>&gt; </span>&#123;</span><br><span class="line"> </span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> RequestBody <span class="title">convert</span><span class="params">(File file)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> RequestBody.create(MediaType.parse(<span class="string">"application/otcet-stream"</span>), file);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>在创建 Retrofit 的时候记得配置上它：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">addConverterFactory(<span class="keyword">new</span> FileRequestBodyConverterFactory())</span><br></pre></td></tr></table></figure></p>
<p>这样，我们的文件内容就能上传了。来，看下结果吧：</p>
<p>RAW BODY<br><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">--25258f46-48b0-4a6b-a617-15318c168ed4</span><br><span class="line">Content-Disposition: form-data; name="description"</span><br><span class="line">Content-Transfer-Encoding: binary</span><br><span class="line">Content-Type: multipart/form-data; charset=utf-8</span><br><span class="line">Content-Length: 21</span><br><span class="line"></span><br><span class="line">This is a description</span><br><span class="line">--25258f46-48b0-4a6b-a617-15318c168ed4</span><br><span class="line">//注意看这里，filename 没了</span><br><span class="line">Content-Disposition: form-data; name="aFile"</span><br><span class="line">//多了这一句</span><br><span class="line">Content-Transfer-Encoding: binary</span><br><span class="line">Content-Type: application/otcet-stream</span><br><span class="line">Content-Length: 32</span><br><span class="line"></span><br><span class="line">Visit me: http://www.println.net</span><br><span class="line">--25258f46-48b0-4a6b-a617-15318c168ed4--</span><br></pre></td></tr></table></figure></p>
<p>文件内容成功上传了，当然其中还存在一些问题，这个目前直接使用 Retrofit 的 Converter 还做不到，原因主要在于我们没有办法通过 Converter 直接将 File 转换为 MultiPartBody.Part，如果想要做到这一点，我们可以对 Retrofit 的源码稍作修改，这个我们后面再谈。</p>
<h4 id="ResponseBodyConverter"><a href="#ResponseBodyConverter" class="headerlink" title="ResponseBodyConverter"></a>ResponseBodyConverter</h4><p>前面我们为大家简单示例了如何自定义 RequestBodyConverter，对应的，Retrofit 也支持自定义 ResponseBodyConverter。</p>
<p>我们再来看下我们定义的接口：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">GitHubService</span> </span>&#123;</span><br><span class="line">  <span class="meta">@GET</span>(<span class="string">"users/&#123;user&#125;/repos"</span>)</span><br><span class="line">  Call&lt;List&lt;Repo&gt;&gt; listRepos(<span class="meta">@Path</span>(<span class="string">"user"</span>) String user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>返回值的类型为 List<repo>，而我们直接拿到的原始返回肯定就是字符串（或者字节流），那么这个返回值类型是怎么来的呢？首先说明的一点是，GitHub 的这个 api 返回的是 Json 字符串，也就是说，我们需要使用 Json 反序列化得到 List<repo>，这其中用到的其实是 GsonResponseBodyConverter。</repo></repo></p>
<p>问题来了，如果请求得到的 Json 字符串与返回值类型不对应，比如：</p>
<p>接口返回的 Json 字符串：<br><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;<span class="attr">"err"</span>:<span class="number">0</span>, <span class="attr">"content"</span>:<span class="string">"This is a content."</span>, <span class="attr">"message"</span>:<span class="string">"OK"</span>&#125;</span><br></pre></td></tr></table></figure></p>
<p>返回值类型:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Result</span></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> code;<span class="comment">//等价于 err</span></span><br><span class="line">    String body;<span class="comment">//等价于 content</span></span><br><span class="line">    String msg;<span class="comment">//等价于 message</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>哇，这时候肯定有人想说，你是不是脑残，偏偏跟服务端对着干？哈哈，我只是示例嘛，而且在生产环境中，你敢保证这种情况不会发生？？<br>这种情况下， Gson 就是再牛逼，也只能默默无语俩眼泪了，它哪儿知道字段的映射关系怎么这么任性啊。好，现在让我们自定义一个 Converter 来解决这个问题吧！<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ArbitraryResponseBodyConverterFactory</span> <span class="keyword">extends</span> <span class="title">Converter</span>.<span class="title">Factory</span></span>&#123;</span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> Converter&lt;ResponseBody, ?&gt; responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">super</span>.responseBodyConverter(type, annotations, retrofit);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ArbitraryResponseBodyConverter</span> <span class="keyword">implements</span> <span class="title">Converter</span>&lt;<span class="title">ResponseBody</span>, <span class="title">Result</span>&gt;</span>&#123;</span><br><span class="line"> </span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> Result <span class="title">convert</span><span class="params">(ResponseBody value)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    RawResult rawResult = <span class="keyword">new</span> Gson().fromJson(value.string(), RawResult.class);</span><br><span class="line">    Result result = <span class="keyword">new</span> Result();</span><br><span class="line">    result.body = rawResult.content;</span><br><span class="line">    result.code = rawResult.err;</span><br><span class="line">    result.msg = rawResult.message;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">RawResult</span></span>&#123;</span><br><span class="line">  <span class="keyword">int</span> err;</span><br><span class="line">  String content;</span><br><span class="line">  String message;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>当然，别忘了在构造 Retrofit 的时候添加这个 Converter，这样我们就能够愉快的让接口返回 Result 对象了。</p>
<p>注意！！Retrofit 在选择合适的 Converter 时，主要依赖于需要转换的对象类型，在添加 Converter 时，注意 Converter 支持的类型的包含关系以及其顺序。</p>
<h2 id="Retrofit-原理剖析"><a href="#Retrofit-原理剖析" class="headerlink" title="Retrofit 原理剖析"></a>Retrofit 原理剖析</h2><p>前一个小节我们把 Retrofit 的基本用法和概念介绍了一下，如果你的目标是学会如何使用它，那么下面的内容你可以不用看了。</p>
<p>不过呢，我就知道你不是那种浅尝辄止的人！这一节我们主要把注意力放在 Retrofit 背后的魔法上面~~<br><img src="https://raw.githubusercontent.com/way1989/way1989.github.io/hexo/images_post/Retrofit/5.jpg"></p>
<h3 id="是谁实际上完成了接口请求的处理？"><a href="#是谁实际上完成了接口请求的处理？" class="headerlink" title="是谁实际上完成了接口请求的处理？"></a>是谁实际上完成了接口请求的处理？</h3><p>前面讲了这么久，我们始终只看到了我们自己定义的接口，比如：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">GitHubService</span> </span>&#123;</span><br><span class="line">  <span class="meta">@GET</span>(<span class="string">"users/&#123;user&#125;/repos"</span>)</span><br><span class="line">  Call&lt;List&lt;Repo&gt;&gt; listRepos(<span class="meta">@Path</span>(<span class="string">"user"</span>) String user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>而真正我使用的时候肯定不能是接口啊，这个神秘的家伙究竟是谁？其实它是 Retrofit 创建的一个代理对象了，这里涉及点儿 Java 的动态代理的知识，直接来看代码：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; <span class="function">T <span class="title">create</span><span class="params">(<span class="keyword">final</span> Class&lt;T&gt; service)</span> </span>&#123;</span><br><span class="line">  Utils.validateServiceInterface(service);</span><br><span class="line">  <span class="keyword">if</span> (validateEagerly) &#123;</span><br><span class="line">    eagerlyValidateMethods(service);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//这里返回一个 service 的代理对象</span></span><br><span class="line">  <span class="keyword">return</span> (T) Proxy.newProxyInstance(service.getClassLoader(), <span class="keyword">new</span> Class&lt;?&gt;[] &#123; service &#125;,</span><br><span class="line">      <span class="keyword">new</span> InvocationHandler() &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> Platform platform = Platform.get();</span><br><span class="line"> </span><br><span class="line">        <span class="meta">@Override</span> <span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object... args)</span></span></span><br><span class="line"><span class="function">            <span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line">          <span class="comment">// If the method is a method from Object then defer to normal invocation.</span></span><br><span class="line">          <span class="keyword">if</span> (method.getDeclaringClass() == Object.class) &#123;</span><br><span class="line">            <span class="keyword">return</span> method.invoke(<span class="keyword">this</span>, args);</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="comment">//DefaultMethod 是 Java 8 的概念，是定义在 interface 当中的有实现的方法</span></span><br><span class="line">          <span class="keyword">if</span> (platform.isDefaultMethod(method)) &#123;</span><br><span class="line">            <span class="keyword">return</span> platform.invokeDefaultMethod(method, service, proxy, args);</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="comment">//每一个接口最终实例化成一个 ServiceMethod，并且会缓存</span></span><br><span class="line">          ServiceMethod serviceMethod = loadServiceMethod(method);</span><br><span class="line">           </span><br><span class="line">          <span class="comment">//由此可见 Retrofit 与 OkHttp 完全耦合，不可分割</span></span><br><span class="line">          OkHttpCall okHttpCall = <span class="keyword">new</span> OkHttpCall&lt;&gt;(serviceMethod, args);</span><br><span class="line">          <span class="comment">//下面这一句当中会发起请求，并解析服务端返回的结果</span></span><br><span class="line">          <span class="keyword">return</span> serviceMethod.callAdapter.adapt(okHttpCall);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>简单的说，在我们调用 GitHubService.listRepos 时，实际上调用的是这里的 InvocationHandler.invoke 方法~~</p>
<h3 id="来一发完整的请求处理流程"><a href="#来一发完整的请求处理流程" class="headerlink" title="来一发完整的请求处理流程"></a>来一发完整的请求处理流程</h3><p>前面我们已经看到 Retrofit 为我们构造了一个 OkHttpCall ，实际上每一个 OkHttpCall 都对应于一个请求，它主要完成最基础的网络请求，而我们在接口的返回中看到的 Call 默认情况下就是 OkHttpCall 了，如果我们添加了自定义的 callAdapter，那么它就会将 OkHttp 适配成我们需要的返回值，并返回给我们。</p>
<p>先来看下 Call 的接口：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Call</span>&lt;<span class="title">T</span>&gt; <span class="keyword">extends</span> <span class="title">Cloneable</span> </span>&#123;</span><br><span class="line">  <span class="comment">//同步发起请求</span></span><br><span class="line">  <span class="function">Response&lt;T&gt; <span class="title">execute</span><span class="params">()</span> <span class="keyword">throws</span> IOException</span>;</span><br><span class="line">  <span class="comment">//异步发起请求，结果通过回调返回</span></span><br><span class="line">  <span class="function"><span class="keyword">void</span> <span class="title">enqueue</span><span class="params">(Callback&lt;T&gt; callback)</span></span>;</span><br><span class="line">  <span class="function"><span class="keyword">boolean</span> <span class="title">isExecuted</span><span class="params">()</span></span>;</span><br><span class="line">  <span class="function"><span class="keyword">void</span> <span class="title">cancel</span><span class="params">()</span></span>;</span><br><span class="line">  <span class="function"><span class="keyword">boolean</span> <span class="title">isCanceled</span><span class="params">()</span></span>;</span><br><span class="line">  <span class="function">Call&lt;T&gt; <span class="title">clone</span><span class="params">()</span></span>;</span><br><span class="line">  <span class="comment">//返回原始请求</span></span><br><span class="line">  <span class="function">Request <span class="title">request</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>我们在使用接口时，大家肯定还记得这一句：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Call&lt;List&lt;Repo&gt;&gt; repos = service.listRepos(<span class="string">"octocat"</span>);</span><br><span class="line">List&lt;Repo&gt; data = repos.execute();</span><br></pre></td></tr></table></figure></p>
<p>这个 repos 其实就是一个 OkHttpCall 实例，execute 就是要发起网络请求。<br>OkHttpCall.execute<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span> <span class="function"><span class="keyword">public</span> Response&lt;T&gt; <span class="title">execute</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">  <span class="comment">//这个 call 是真正的 OkHttp 的 call，本质上 OkHttpCall 只是对它做了一层封装</span></span><br><span class="line">  okhttp3.Call call;</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line">    <span class="comment">//处理重复执行的逻辑</span></span><br><span class="line">    <span class="keyword">if</span> (executed) <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"Already executed."</span>);</span><br><span class="line">    executed = <span class="keyword">true</span>;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">if</span> (creationFailure != <span class="keyword">null</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (creationFailure <span class="keyword">instanceof</span> IOException) &#123;</span><br><span class="line">        <span class="keyword">throw</span> (IOException) creationFailure;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> (RuntimeException) creationFailure;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">     </span><br><span class="line">    call = rawCall;</span><br><span class="line">    <span class="keyword">if</span> (call == <span class="keyword">null</span>) &#123;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        call = rawCall = createRawCall();</span><br><span class="line">      &#125; <span class="keyword">catch</span> (IOException | RuntimeException e) &#123;</span><br><span class="line">        creationFailure = e;</span><br><span class="line">        <span class="keyword">throw</span> e;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">if</span> (canceled) &#123;</span><br><span class="line">    call.cancel();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//发起请求，并解析结果</span></span><br><span class="line">  <span class="keyword">return</span> parseResponse(call.execute());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>我们看到 OkHttpCall 其实也是封装了 okhttp3.Call，在这个方法中，我们通过 okhttp3.Call 发起了进攻，额，发起了请求。有关 OkHttp 的内容，我在这里就不再展开了。<br>parseResponse 主要完成了由 okhttp3.Response 向 retrofit.Response 的转换，同时也处理了对原始返回的解析：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Response&lt;T&gt; <span class="title">parseResponse</span><span class="params">(okhttp3.Response rawResponse)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">  ResponseBody rawBody = rawResponse.body();</span><br><span class="line"> </span><br><span class="line">  <span class="comment">//略掉一些代码</span></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">//在这里完成了原始 Response 的解析，T 就是我们想要的结果，比如 GitHubService.listRepos 的 List&lt;Repo&gt;</span></span><br><span class="line">    T body = serviceMethod.toResponse(catchingBody);</span><br><span class="line">    <span class="keyword">return</span> Response.success(body, rawResponse);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (RuntimeException e) &#123;</span><br><span class="line">    <span class="comment">// If the underlying source threw an exception, propagate that rather than indicating it was</span></span><br><span class="line">    <span class="comment">// a runtime exception.</span></span><br><span class="line">    catchingBody.throwIfCaught();</span><br><span class="line">    <span class="keyword">throw</span> e;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>至此，我们就拿到了我们想要的数据~~</p>
<h3 id="结果适配，你是不是想用-RxJava？"><a href="#结果适配，你是不是想用-RxJava？" class="headerlink" title="结果适配，你是不是想用 RxJava？"></a>结果适配，你是不是想用 RxJava？</h3><p>前面我们已经提到过 CallAdapter 的事儿，默认情况下，它并不会对 OkHttpCall 实例做任何处理：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">DefaultCallAdapterFactory</span> <span class="keyword">extends</span> <span class="title">CallAdapter</span>.<span class="title">Factory</span> </span>&#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">final</span> CallAdapter.Factory INSTANCE = <span class="keyword">new</span> DefaultCallAdapterFactory();</span><br><span class="line"> </span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> CallAdapter&lt;?&gt; get(Type returnType, Annotation[] annotations, Retrofit retrofit) &#123;</span><br><span class="line">    ... 毫不留情的省略一些代码 ...</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> CallAdapter&lt;Call&lt;?&gt;&gt;() &#123;</span><br><span class="line">      ... 省略一些代码 ...</span><br><span class="line"> </span><br><span class="line">      <span class="meta">@Override</span> <span class="keyword">public</span> &lt;R&gt; <span class="function">Call&lt;R&gt; <span class="title">adapt</span><span class="params">(Call&lt;R&gt; call)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//看这里，直接把传入的 call 返回了</span></span><br><span class="line">        <span class="keyword">return</span> call;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>现在的需求是，我想要接入 RxJava，让接口的返回结果改为 Observable：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">GitHub</span> </span>&#123;</span><br><span class="line">  <span class="meta">@GET</span>(<span class="string">"/repos/&#123;owner&#125;/&#123;repo&#125;/contributors"</span>)</span><br><span class="line">  Observable&lt;List&lt;Contributor&gt;&gt; contributors(</span><br><span class="line">      <span class="meta">@Path</span>(<span class="string">"owner"</span>) String owner,</span><br><span class="line">      <span class="meta">@Path</span>(<span class="string">"repo"</span>) String repo);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>可不可以呢？当然是可以的，只需要提供一个 Adapter，将 OkHttpCall 转换为 Observable 即可呀！Retrofit 的开发者们早就想到了这个问题，并且为我们提供了相应的 Adapter：<br>RxJavaCallAdapterFactory<br>我们只需要在构造 Retrofit 时，添加它：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">addCallAdapterFactory(RxJavaCallAdapterFactory.create())</span><br></pre></td></tr></table></figure></p>
<p>这样我们的接口就可以以 RxJava 的方式工作了。</p>
<p>好，歇会儿，抽一袋烟。。。</p>
<p>接着我们搞清楚 RxJavaCallAdapterFactory 是怎么工作的，首先让我们来看下 CallAdapter 的接口：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">CallAdapter</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">   *返回 Http 返回解析后的类型。需要注意的是这个并不是接口的返回类型，</span></span><br><span class="line"><span class="comment">   *而是接口返回类型中的泛型参数的实参。</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="function">Type <span class="title">responseType</span><span class="params">()</span></span>;</span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">   * T 是我们需要转换成的接口返回类型，参数 call 其实最初就是 OkHttpCall 的实例</span></span><br><span class="line"><span class="comment">   * 在这里 T 其实是 RxJava 支持的类型，比如 Observable</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  &lt;R&gt; <span class="function">T <span class="title">adapt</span><span class="params">(Call&lt;R&gt; call)</span></span>;</span><br><span class="line"> </span><br><span class="line">  <span class="comment">//我们需要将 Factory 的子类对应的实例在构造 Retrofit 时添加到其中。</span></span><br><span class="line">  <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Factory</span> </span>&#123;</span><br><span class="line">    <span class="comment">//根据接口的返回类型（Observable&lt;T&gt;），注解类型等等来判断是否是当前 Adapter 支持的类型，不是则返回null</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> CallAdapter&lt;?&gt; get(Type returnType, Annotation[] annotations,</span><br><span class="line">        Retrofit retrofit);</span><br><span class="line"> </span><br><span class="line">    <span class="comment">//获取指定 index 的泛型参数的上限，比如对于 Map&lt;String, ? extends Number&gt;，index为 1 的参数上限是 Number</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">static</span> Type <span class="title">getParameterUpperBound</span><span class="params">(<span class="keyword">int</span> index, ParameterizedType type)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> Utils.getParameterUpperBound(index, type);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * 获取原始类型，比如 List&lt;String&gt; 返回 List.class，这里传入的 type 情况可能比较复杂，因此不能直接当做</span></span><br><span class="line"><span class="comment">     * Class 去做判断。这个方法在判断类型是否为支持的类型时经常用到。</span></span><br><span class="line"><span class="comment">    protected static Class&lt;?&gt; getRawType(Type type) &#123;</span></span><br><span class="line"><span class="comment">      return Utils.getRawType(type);</span></span><br><span class="line"><span class="comment">    &#125;</span></span><br><span class="line"><span class="comment">  &#125;</span></span><br><span class="line"><span class="comment">&#125;</span></span><br></pre></td></tr></table></figure></p>
<p>代码中做了较为详细的注释，简单来说，我们只需要实现 CallAdapter 类来提供具体的适配逻辑，并实现相应的 Factory，用来将当前的 CallAdapter注册到 Retrofit 当中，并在 Factory.get 方法中根据类型来返回当前的 CallAdapter即可。知道了这些，我们再来看 RxJavaCallAdapterFactory：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">RxJavaCallAdapterFactory</span> <span class="keyword">extends</span> <span class="title">CallAdapter</span>.<span class="title">Factory</span> </span>&#123;</span><br><span class="line"> </span><br><span class="line">  ... 请叫我省略君，为了省地方，一个都不放过！ ...</span><br><span class="line"> </span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> CallAdapter&lt;?&gt; get(Type returnType, Annotation[] annotations, Retrofit retrofit) &#123;</span><br><span class="line">    <span class="comment">//注意下面的代码主要是判断 returnType 是否为 RxJava 支持的类型</span></span><br><span class="line">    Class&lt;?&gt; rawType = getRawType(returnType);</span><br><span class="line">    String canonicalName = rawType.getCanonicalName();</span><br><span class="line">    <span class="keyword">boolean</span> isSingle = <span class="string">"rx.Single"</span>.equals(canonicalName);</span><br><span class="line">    <span class="keyword">boolean</span> isCompletable = <span class="string">"rx.Completable"</span>.equals(canonicalName);</span><br><span class="line">    <span class="keyword">if</span> (rawType != Observable.class &amp;&amp; !isSingle &amp;&amp; !isCompletable) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    ... 这里省略掉的代码主要是根据返回类型获取合适的 Adapter ...</span><br><span class="line">    <span class="keyword">return</span> callAdapter;</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  ... 我又来了，继续略去一些代码 ...</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleCallAdapter</span> <span class="keyword">implements</span> <span class="title">CallAdapter</span>&lt;<span class="title">Observable</span>&lt;?&gt;&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Type responseType;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Scheduler scheduler;</span><br><span class="line"> </span><br><span class="line">    SimpleCallAdapter(Type responseType, Scheduler scheduler) &#123;</span><br><span class="line">      <span class="keyword">this</span>.responseType = responseType;</span><br><span class="line">      <span class="keyword">this</span>.scheduler = scheduler;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span> <span class="function"><span class="keyword">public</span> Type <span class="title">responseType</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> responseType;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span> <span class="keyword">public</span> &lt;R&gt; <span class="function">Observable&lt;R&gt; <span class="title">adapt</span><span class="params">(Call&lt;R&gt; call)</span> </span>&#123;</span><br><span class="line">      <span class="comment">//在这里创建需作为返回值的 Observable 实例，并持有 call 实例</span></span><br><span class="line">      <span class="comment">//可以想象得到，在 Observable.subscribe 触发时， call.execute 将会被调用</span></span><br><span class="line">      Observable&lt;R&gt; observable = Observable.create(<span class="keyword">new</span> CallOnSubscribe&lt;&gt;(call)) </span><br><span class="line">          .lift(OperatorMapResponseToBodyOrError.&lt;R&gt;instance());</span><br><span class="line">      <span class="keyword">if</span> (scheduler != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> observable.subscribeOn(scheduler);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> observable;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  <span class="comment">//... 略去一些代码 ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>RxJavaCallAdapterFactory 提供了不止一种 Adapter，但原理大同小异，有兴趣的读者可以自行参阅其源码。</p>
<p>至此，我们已经对 CallAdapter 的机制有了一个清晰的认识了。</p>
<h2 id="几个进阶玩法"><a href="#几个进阶玩法" class="headerlink" title="几个进阶玩法"></a>几个进阶玩法</h2><p>前面我们已经介绍了很多东西了。。可，挖掘机专业的同学们，你们觉得这就够了么？当然是不够！</p>
<h3 id="继续简化文件上传的接口"><a href="#继续简化文件上传的接口" class="headerlink" title="继续简化文件上传的接口"></a>继续简化文件上传的接口</h3><p>在前面我们曾试图简化文件上传接口的使用，尽管我们已经给出了相应的 File -&gt; RequestBody 的 Converter，不过基于 Retrofit本身的限制，我们还是不能像直接构造 MultiPartBody.Part 那样来获得更多的灵活性。这时候该怎么办？当然是 Hack~~</p>
<p>首先明确我们的需求：</p>
<ul>
<li>文件的 Content-Type 需要更多的灵活性，不应该写死在 Converter 当中，可以的话，最好可以根据文件的扩展名来映射出来对应的 Content-Type， 比如 image.png -&gt; image/png；</li>
<li>在请求的数据中，能够正常携带 filename 这个字段。</li>
</ul>
<p>为此，我增加了一套完整的参数解析方案：</p>
<ul>
<li>增加任意类型转换的 Converter，这一步主要是满足后续我们直接将入参类型转换为 MultiPartBody.Part 类型：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Converter</span>&lt;<span class="title">F</span>, <span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">  ...</span><br><span class="line">   </span><br><span class="line">  <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Factory</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">//返回一个满足条件的不限制类型的 Converter</span></span><br><span class="line">    <span class="keyword">public</span> Converter&lt;?, ?&gt; arbitraryConverter(Type originalType,</span><br><span class="line">          Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit)&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>需要注意的是，Retrofit 类当中也需要增加相应的方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;F, T&gt; <span class="function">Converter&lt;F, T&gt; <span class="title">arbitraryConverter</span><span class="params">(Type orignalType,</span></span></span><br><span class="line"><span class="function"><span class="params">                                                 Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> nextArbitraryConverter(<span class="keyword">null</span>, orignalType, convertedType, parameterAnnotations, methodAnnotations);</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> &lt;F, T&gt; <span class="function">Converter&lt;F, T&gt; <span class="title">nextArbitraryConverter</span><span class="params">(Converter.Factory skipPast,</span></span></span><br><span class="line"><span class="function"><span class="params">                              Type type, Type convertedType,  Annotation[] parameterAnnotations, Annotation[] methodAnnotations)</span> </span>&#123;</span><br><span class="line">  checkNotNull(type, <span class="string">"type == null"</span>);</span><br><span class="line">  checkNotNull(parameterAnnotations, <span class="string">"parameterAnnotations == null"</span>);</span><br><span class="line">  checkNotNull(methodAnnotations, <span class="string">"methodAnnotations == null"</span>);</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">int</span> start = converterFactories.indexOf(skipPast) + <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">int</span> i = start, count = converterFactories.size(); i &lt; count; i++) &#123;</span><br><span class="line">    Converter.Factory factory = converterFactories.get(i);</span><br><span class="line">    Converter&lt;?, ?&gt; converter =</span><br><span class="line">            factory.arbitraryConverter(type, convertedType, parameterAnnotations, methodAnnotations, <span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">if</span> (converter != <span class="keyword">null</span>) &#123;</span><br><span class="line">      <span class="comment">//noinspection unchecked</span></span><br><span class="line">      <span class="keyword">return</span> (Converter&lt;F, T&gt;) converter;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<ul>
<li>再给出 arbitraryConverter 的具体实现：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TypedFileMultiPartBodyConverterFactory</span> <span class="keyword">extends</span> <span class="title">Converter</span>.<span class="title">Factory</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Converter&lt;TypedFile, MultipartBody.Part&gt; arbitraryConverter(Type originalType, Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) &#123;</span><br><span class="line">        <span class="keyword">if</span> (originalType == TypedFile.class &amp;&amp; convertedType == MultipartBody.Part.class) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> FileRequestBodyConverter();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TypedFileMultiPartBodyConverter</span> <span class="keyword">implements</span> <span class="title">Converter</span>&lt;<span class="title">TypedFile</span>, <span class="title">MultipartBody</span>.<span class="title">Part</span>&gt; </span>&#123;</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> MultipartBody.<span class="function">Part <span class="title">convert</span><span class="params">(TypedFile typedFile)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        RequestBody requestFile =</span><br><span class="line">                RequestBody.create(typedFile.getMediaType(), typedFile.getFile());</span><br><span class="line">        <span class="keyword">return</span> MultipartBody.Part.createFormData(typedFile.getName(), typedFile.getFile().getName(), requestFile);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TypedFile</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> MediaType mediaType;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> File file;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">TypedFile</span><span class="params">(String name, String filepath)</span></span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(name, <span class="keyword">new</span> File(filepath));</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">TypedFile</span><span class="params">(String name, File file)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(MediaType.parse(MediaTypes.getMediaType(file)), name, file);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">TypedFile</span><span class="params">(MediaType mediaType, String name, String filepath)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(mediaType, name, <span class="keyword">new</span> File(filepath));</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">TypedFile</span><span class="params">(MediaType mediaType, String name, File file)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.mediaType = mediaType;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">        <span class="keyword">this</span>.file = file;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> MediaType <span class="title">getMediaType</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> mediaType;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> File <span class="title">getFile</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> file;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<ul>
<li>在声明接口时，@Part 不要传入参数，这样 Retrofit 在 ServiceMethod.Builder.parseParameterAnnotation 方法中解析 Part时，就会认为我们传入的参数为 MultiPartBody.Part 类型（实际上我们将在后面自己转换）。那么解析的时候，我们拿到前面定义好的 Converter，构造一个 ParameterHandler：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) &#123;</span><br><span class="line">    <span class="keyword">return</span> ParameterHandler.RawPart.INSTANCE;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    Converter&lt;?, ?&gt; converter =</span><br><span class="line">            retrofit.arbitraryConverter(type, MultipartBody.Part.class, annotations, methodAnnotations);</span><br><span class="line">    <span class="keyword">if</span>(converter == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> parameterError(p,</span><br><span class="line">                <span class="string">"@Part annotation must supply a name or use MultipartBody.Part parameter type."</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> ParameterHandler.TypedFileHandler((Converter&lt;TypedFile, MultipartBody.Part&gt;) converter);</span><br><span class="line">&#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TypedFileHandler</span> <span class="keyword">extends</span> <span class="title">ParameterHandler</span>&lt;<span class="title">TypedFile</span>&gt;</span>&#123;</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> Converter&lt;TypedFile, MultipartBody.Part&gt; converter;</span><br><span class="line"> </span><br><span class="line">  TypedFileHandler(Converter&lt;TypedFile, MultipartBody.Part&gt; converter) &#123;</span><br><span class="line">    <span class="keyword">this</span>.converter = converter;</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">void</span> <span class="title">apply</span><span class="params">(RequestBuilder builder, TypedFile value)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(value != <span class="keyword">null</span>)&#123;</span><br><span class="line">      builder.addPart(converter.convert(value));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<ul>
<li>这时候再看我们的接口声明：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">FileUploadService</span> </span>&#123;</span><br><span class="line">  <span class="meta">@Multipart</span></span><br><span class="line">  <span class="meta">@POST</span>(<span class="string">"upload"</span>)</span><br><span class="line">  <span class="function">Call&lt;ResponseBody&gt; <span class="title">upload</span><span class="params">(@Part(<span class="string">"description"</span>)</span> RequestBody description,</span></span><br><span class="line"><span class="function">                            @Part TypedFile typedFile)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>以及使用方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Retrofit retrofit = <span class="keyword">new</span> Retrofit.Builder()</span><br><span class="line">    .baseUrl(<span class="string">"http://www.println.net/"</span>)</span><br><span class="line">    .addConverterFactory(<span class="keyword">new</span> TypedFileMultiPartBodyConverterFactory())</span><br><span class="line">    .addConverterFactory(GsonConverterFactory.create())</span><br><span class="line">    .build();</span><br><span class="line"> </span><br><span class="line">FileUploadService service = retrofit.create(FileUploadService.class);</span><br><span class="line">TypedFile typedFile = <span class="keyword">new</span> TypedFile(<span class="string">"aFile"</span>, filename);</span><br><span class="line">String descriptionString = <span class="string">"This is a description"</span>;</span><br><span class="line">RequestBody description =</span><br><span class="line">        RequestBody.create(</span><br><span class="line">                MediaType.parse(<span class="string">"multipart/form-data"</span>), descriptionString);</span><br><span class="line"> </span><br><span class="line">Call&lt;ResponseBody&gt; call = service.upload(description, typedFile);</span><br><span class="line">call.enqueue(...);</span><br></pre></td></tr></table></figure></p>
<p>至此，我们已经通过自己的双手，让 Retrofit 的点亮了自定义上传文件的技能，风骚等级更上一层楼！</p>
<h3 id="Mock-Server"><a href="#Mock-Server" class="headerlink" title="Mock Server"></a>Mock Server</h3><p>我们在开发过程中，经常遇到服务端不稳定的情况，测试开发环境，这是难免的。于是我们需要能够模拟网络请求来调试我们的客户端逻辑，Retrofit 自然是支持这个功能的。</p>
<p>真是太贴心，Retrofit 提供了一个 MockServer 的功能，可以在几乎不改动客户端原有代码的前提下，实现接口数据返回的自定义，我们在自己的工程中增加下面的依赖：<br><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">compile <span class="string">'com.squareup.retrofit2:retrofit-mock:2.0.2</span></span><br></pre></td></tr></table></figure></p>
<p>还是先让我们来看看官方 demo，首先定义了一个 GituHb api，好熟悉的感觉：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">GitHub</span> </span>&#123;</span><br><span class="line">  <span class="meta">@GET</span>(<span class="string">"/repos/&#123;owner&#125;/&#123;repo&#125;/contributors"</span>)</span><br><span class="line">  Call&lt;List&lt;Contributor&gt;&gt; contributors(</span><br><span class="line">      <span class="meta">@Path</span>(<span class="string">"owner"</span>) String owner,</span><br><span class="line">      <span class="meta">@Path</span>(<span class="string">"repo"</span>) String repo);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这就是我们要请求的接口了，怎么 Mock 呢？</p>
<ul>
<li>定义一个接口实现类 MockGitHub，我们可以看到，所有我们需要请求的接口都在这里得到了实现，也就是说，我们待会儿调用 GitHub 的 api 时，实际上是访问 MockGitHub 的方法：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">MockGitHub</span> <span class="keyword">implements</span> <span class="title">GitHub</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BehaviorDelegate&lt;GitHub&gt; delegate;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Map&lt;String, List&lt;Contributor&gt;&gt;&gt; ownerRepoContributors;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">MockGitHub</span><span class="params">(BehaviorDelegate&lt;GitHub&gt; delegate)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">this</span>.delegate = delegate;</span><br><span class="line">      ownerRepoContributors = <span class="keyword">new</span> LinkedHashMap&lt;&gt;();</span><br><span class="line"> </span><br><span class="line">      <span class="comment">// Seed some mock data.</span></span><br><span class="line">      addContributor(<span class="string">"square"</span>, <span class="string">"retrofit"</span>, <span class="string">"John Doe"</span>, <span class="number">12</span>);</span><br><span class="line">      addContributor(<span class="string">"square"</span>, <span class="string">"retrofit"</span>, <span class="string">"Bob Smith"</span>, <span class="number">2</span>);</span><br><span class="line">      addContributor(<span class="string">"square"</span>, <span class="string">"retrofit"</span>, <span class="string">"Big Bird"</span>, <span class="number">40</span>);</span><br><span class="line">      addContributor(<span class="string">"square"</span>, <span class="string">"picasso"</span>, <span class="string">"Proposition Joe"</span>, <span class="number">39</span>);</span><br><span class="line">      addContributor(<span class="string">"square"</span>, <span class="string">"picasso"</span>, <span class="string">"Keiser Soze"</span>, <span class="number">152</span>);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span> <span class="keyword">public</span> Call&lt;List&lt;Contributor&gt;&gt; contributors(String owner, String repo) &#123;</span><br><span class="line">      List&lt;Contributor&gt; response = Collections.emptyList();</span><br><span class="line">      Map&lt;String, List&lt;Contributor&gt;&gt; repoContributors = ownerRepoContributors.get(owner);</span><br><span class="line">      <span class="keyword">if</span> (repoContributors != <span class="keyword">null</span>) &#123;</span><br><span class="line">        List&lt;Contributor&gt; contributors = repoContributors.get(repo);</span><br><span class="line">        <span class="keyword">if</span> (contributors != <span class="keyword">null</span>) &#123;</span><br><span class="line">          response = contributors;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> delegate.returningResponse(response).contributors(owner, repo);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addContributor</span><span class="params">(String owner, String repo, String name, <span class="keyword">int</span> contributions)</span> </span>&#123;</span><br><span class="line">      Map&lt;String, List&lt;Contributor&gt;&gt; repoContributors = ownerRepoContributors.get(owner);</span><br><span class="line">      <span class="keyword">if</span> (repoContributors == <span class="keyword">null</span>) &#123;</span><br><span class="line">        repoContributors = <span class="keyword">new</span> LinkedHashMap&lt;&gt;();</span><br><span class="line">        ownerRepoContributors.put(owner, repoContributors);</span><br><span class="line">      &#125;</span><br><span class="line">      List&lt;Contributor&gt; contributors = repoContributors.get(repo);</span><br><span class="line">      <span class="keyword">if</span> (contributors == <span class="keyword">null</span>) &#123;</span><br><span class="line">        contributors = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">        repoContributors.put(repo, contributors);</span><br><span class="line">      &#125;</span><br><span class="line">      contributors.add(<span class="keyword">new</span> Contributor(name, contributions));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<ul>
<li>构建 Mock Server 对象：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Create a very simple Retrofit adapter which points the GitHub API.</span></span><br><span class="line">Retrofit retrofit = <span class="keyword">new</span> Retrofit.Builder()</span><br><span class="line">    .baseUrl(SimpleService.API_URL)</span><br><span class="line">    .build();</span><br><span class="line"> </span><br><span class="line"><span class="comment">// Create a MockRetrofit object with a NetworkBehavior which manages the fake behavior of calls.</span></span><br><span class="line">NetworkBehavior behavior = NetworkBehavior.create();</span><br><span class="line">MockRetrofit mockRetrofit = <span class="keyword">new</span> MockRetrofit.Builder(retrofit)</span><br><span class="line">    .networkBehavior(behavior)</span><br><span class="line">    .build();</span><br><span class="line"> </span><br><span class="line">BehaviorDelegate&lt;GitHub&gt; delegate = mockRetrofit.create(GitHub.class);</span><br><span class="line">MockGitHub gitHub = <span class="keyword">new</span> MockGitHub(delegate);</span><br></pre></td></tr></table></figure>
<ul>
<li>使用 Mock Server ：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Call&lt;List&lt;Contributor&gt;&gt; contributors = gitHub.contributors(owner, repo);</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>也就是说，我们完全可以自己造一个假的数据源，通过 Mock Server 来返回这些写数据。</p>
<p>那么问题来了，这其实并没有完全模拟网络请求的解析流程，如果我只能提供原始的 json 字符串，怎么通过 Retrofit 来实现 Mock Server？</p>
<p>时间已经不早啦，我就不猥琐发育了，直接推塔~</p>
<p>本文前面一直专注于介绍 Retrofit，很少提及 OkHttp，殊不知 OkHttp 有一套拦截器的机制，也就是说，我们可以任性的检查 Retrofit 即将发出或者正在发出的所有请求，并且篡改它。所以我们只需要找到我们想要的接口，定制自己的返回结果就好了，下面是一段示例：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">OkHttpClient client = <span class="keyword">new</span> OkHttpClient.Builder().addInterceptor(<span class="keyword">new</span> Interceptor() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Response <span class="title">intercept</span><span class="params">(Chain chain)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        Response response = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">if</span>(BuildConfig.DEBUG &amp;&amp; chain.request().url().uri().getPath().equals(<span class="string">"/contributors"</span>)) &#123;</span><br><span class="line">            <span class="comment">//这里读取我们需要返回的 Json 字符串</span></span><br><span class="line">            String responseString = ...;</span><br><span class="line">             </span><br><span class="line">            response = <span class="keyword">new</span> Response.Builder()</span><br><span class="line">                    .code(<span class="number">200</span>)</span><br><span class="line">                    .message(responseString)</span><br><span class="line">                    .request(chain.request())</span><br><span class="line">                    .protocol(Protocol.HTTP_1_0)</span><br><span class="line">                    .body(ResponseBody.create(MediaType.parse(<span class="string">"application/json"</span>), responseString.getBytes()))</span><br><span class="line">                    .addHeader(<span class="string">"content-type"</span>, <span class="string">"application/json"</span>)</span><br><span class="line">                    .build();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            response = chain.proceed(chain.request());</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="keyword">return</span> response;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;).build();</span><br><span class="line"> </span><br><span class="line">Retrofit retrofit = <span class="keyword">new</span> Retrofit.Builder()</span><br><span class="line">        .baseUrl(BASE_URL)</span><br><span class="line">        .addConverterFactory(GsonConverterFactory.create())</span><br><span class="line">        .client(client)</span><br><span class="line">        .build();</span><br></pre></td></tr></table></figure></p>
<p>这样，我们就会拦截 contributors 这个 api 并定制其返回了。</p>
<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>Retrofit 是非常强大的，本文通过丰富的示例和对源码的挖掘，向大家展示了 Retrofit 自身强大的功能以及扩展性，就算它本身功能不能满足你的需求，你也可以很容易的进行改造，毕竟人家的代码真是写的漂亮啊。</p>

    </div>

    
    
    
        <div class="reward-container">
  <div></div>
  <button onclick="var qr = document.getElementById('qr'); qr.style.display = (qr.style.display === 'none') ? 'block' : 'none';">
    打赏
  </button>
  <div id="qr" style="display: none;">
      
      <div style="display: inline-block;">
        <img src="/images/wechatpay.jpg" alt="Broncho 微信支付">
        <p>微信支付</p>
      </div>
      
      <div style="display: inline-block;">
        <img src="/images/alipay.jpg" alt="Broncho 支付宝">
        <p>支付宝</p>
      </div>

  </div>
</div>


      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/博客/" rel="tag"># 博客</a>
              <a href="/tags/Retrofit/" rel="tag"># Retrofit</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2016/09/15/blogs/SensorFramework/" rel="prev" title="Android Sensor框架Framework层解读">
      <i class="fa fa-chevron-left"></i> Android Sensor框架Framework层解读
    </a></div>
      <div class="post-nav-item">
    <a href="/2016/12/15/notes/GitAdvance/" rel="next" title="Git进阶用法">
      Git进阶用法 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          
    
  <div class="comments">
    <div id="lv-container" data-id="city" data-uid="MTAyMC8zNzk3MC8xNDUwMA=="></div>
  </div>
  

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-3"><a class="nav-link" href="#本文转自微信-Bugly"><span class="nav-number">1.</span> <span class="nav-text">本文转自微信: Bugly</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#初识-Retrofit"><span class="nav-number"></span> <span class="nav-text">初识 Retrofit</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Retrofit-概览"><span class="nav-number">1.</span> <span class="nav-text">Retrofit 概览</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Hello-Retrofit"><span class="nav-number">2.</span> <span class="nav-text">Hello Retrofit</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Url-配置"><span class="nav-number">3.</span> <span class="nav-text">Url 配置</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#参数类型"><span class="nav-number">4.</span> <span class="nav-text">参数类型</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#Query-amp-QueryMap"><span class="nav-number">4.1.</span> <span class="nav-text">Query &amp; QueryMap</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Field-amp-FieldMap"><span class="nav-number">4.2.</span> <span class="nav-text">Field &amp; FieldMap</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Part-amp-PartMap"><span class="nav-number">4.3.</span> <span class="nav-text">Part &amp; PartMap</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Converter，让你的入参和返回类型丰富起来"><span class="nav-number">5.</span> <span class="nav-text">Converter，让你的入参和返回类型丰富起来</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#RequestBodyConverter"><span class="nav-number">5.1.</span> <span class="nav-text">RequestBodyConverter</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#ResponseBodyConverter"><span class="nav-number">5.2.</span> <span class="nav-text">ResponseBodyConverter</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Retrofit-原理剖析"><span class="nav-number"></span> <span class="nav-text">Retrofit 原理剖析</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#是谁实际上完成了接口请求的处理？"><span class="nav-number">1.</span> <span class="nav-text">是谁实际上完成了接口请求的处理？</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#来一发完整的请求处理流程"><span class="nav-number">2.</span> <span class="nav-text">来一发完整的请求处理流程</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#结果适配，你是不是想用-RxJava？"><span class="nav-number">3.</span> <span class="nav-text">结果适配，你是不是想用 RxJava？</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#几个进阶玩法"><span class="nav-number"></span> <span class="nav-text">几个进阶玩法</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#继续简化文件上传的接口"><span class="nav-number">1.</span> <span class="nav-text">继续简化文件上传的接口</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Mock-Server"><span class="nav-number">2.</span> <span class="nav-text">Mock Server</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#小结"><span class="nav-number"></span> <span class="nav-text">小结</span></a></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Broncho"
      src="/images/avatar.jpg">
  <p class="site-author-name" itemprop="name">Broncho</p>
  <div class="site-description" itemprop="description">爱生活...爱Android...</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">17</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
        <span class="site-state-item-count">18</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">7</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/way1989" title="GitHub → https://github.com/way1989" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        
  <div class="beian"><a href="https://beian.miit.gov.cn" rel="noopener" target="_blank">粤ICP备20072588号 </a>
      <img src="/images/gongan.png" style="display: inline-block;"><a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=44030702002235" rel="noopener" target="_blank">粤公网安备 44030702002235号 </a>
  </div>

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Broncho</span>
</div>

        








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>
<script src="/js/utils.js"></script><script src="/js/motion.js"></script>
<script src="/js/schemes/muse.js"></script>
<script src="/js/next-boot.js"></script>



  




  <script src="/js/local-search.js"></script>












  

  

<script>
NexT.utils.loadComments(document.querySelector('#lv-container'), () => {
  window.livereOptions = {
    refer: location.pathname.replace(CONFIG.root, '').replace('index.html', '')
  };
  (function(d, s) {
    var j, e = d.getElementsByTagName(s)[0];
    if (typeof LivereTower === 'function') { return; }
    j = d.createElement(s);
    j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
    j.async = true;
    e.parentNode.insertBefore(j, e);
  })(document, 'script');
});
</script>

</body>
</html>
